/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
*/
#include <bits/alltypes.h>
#include <js_native_api.h>
#include <js_native_api_types.h>
#include <string>
#include "VideoCompressor.h"
#include "muxer.h"
#include "DeMuxer.h"
#include "hilog/log.h"
#include "napi/native_api.h"
#include "video/decoder/videodec_ndk_sample.h"
#include "video/encoder/videoenc_ndk_sample.h"
#include "audio/decoder/audiodec_ndk_sample.h"
#include "audio/encoder/audioenc_ndk_sample.h"
#undef LOG_DOMAIN
#undef LOG_TAG
#define LOG_DOMAIN 0x3200 // 全局domain宏，标识业务领域
#define LOG_TAG "videoCompressor" // 全局tag宏，标识模块日志tag

struct AsyncCallbackInfo {
    napi_env env;
    napi_async_work asyncWork;
    napi_deferred deferred;
    int32_t outFd = true;
    int32_t inputFd = true;
    int32_t inputOffset = 0;
    int32_t inputSize = 0;
    int32_t quality = 0;
    int32_t resultCode = 0;
    std::string resultStr = "";
    std::string outputPath = "";
};

napi_value VideoCompressor::JsConstructor(napi_env env, napi_callback_info info)
{
    napi_value targetObj = nullptr;
    void *data = nullptr;
    size_t argsNum = 0;
    napi_value args[2] = {nullptr};
    napi_get_cb_info(env, info, &argsNum, args, &targetObj, &data);
    auto classBind = std::make_unique<VideoCompressor>();
    napi_wrap(
        env, nullptr, classBind.get(),
        [](napi_env env, void *data, void *hint) {
            VideoCompressor *bind = (VideoCompressor *)data;
            delete bind;
            bind = nullptr;
        },
        nullptr, nullptr);
        return targetObj;
}

int32_t CreateMutex(std::unique_ptr<Muxer> &muxer, std::unique_ptr<DeMuxer> &demuxer,
                    AsyncCallbackInfo *asyncCallbackInfo)
{
    muxer->width = demuxer->width;
    muxer->height = demuxer->height;
    muxer->videoMime = demuxer->videoMime;
    muxer->audioMime = demuxer->audioMime;
    muxer->audioChannelCount = demuxer->audioChannelCount;
    muxer->audioBitrate = demuxer->audioBitrate;
    muxer->audioSampleRate = demuxer->audioSampleRate;
    muxer->videoFrameRate = demuxer->frameRate;
    int ret = muxer->CreateMuxer(asyncCallbackInfo->outFd);
    return ret;
}

int32_t CreateVideoEncode(std::unique_ptr<VEncNdkSample> &vEncSample, std::unique_ptr<MutexManager> &mutexManager,
    std::unique_ptr<DeMuxer> &demuxer, std::unique_ptr<Muxer> &muxer, AsyncCallbackInfo *asyncCallbackInfo)
{
    vEncSample->RegisterMuxerManager(mutexManager.get());
    vEncSample->width = demuxer->width;
    vEncSample->height = demuxer->height;
    vEncSample->bitrate = demuxer->bitrate;
    vEncSample->frameRate = demuxer->frameRate;
    vEncSample->videoMime = demuxer->videoMime;
    int ret = vEncSample->CreateVideoEncoder(demuxer->videoMime);
    if (ret != AV_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "CreateVideoEncoder error");
        return ret;
    }
    ret = vEncSample->SetVideoEncoderCallback();
    if (ret != AV_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "VideoEncoder SetVideoEncoderCallback %{public}d", ret);
        return ret;
    }
    ret = vEncSample->ConfigureVideoEncoder(asyncCallbackInfo->quality);
    if (ret != AV_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "VideoEncoder ConfigureVideoEncoder %{public}d", ret);
        return ret;
    }
    ret = vEncSample->GetSurface();
    if (ret != AV_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "VideoEncoder GetSurface %{public}d", ret);
        return ret;
    }
    vEncSample->RegisterMuxer(muxer.get());
    return ret;
}

int32_t CreateAudioEncode(std::unique_ptr<AEncNdkSample> &aEncSample, std::unique_ptr<MutexManager> &mutexManager,
    std::unique_ptr<DeMuxer> &demuxer, std::unique_ptr<Muxer> &muxer)
{
    aEncSample->RegisterMuxerManager(mutexManager.get());
    aEncSample->audioChannelCount = demuxer->audioChannelCount;
    aEncSample->audioSampleRate = demuxer->audioSampleRate;
    aEncSample->audioBitrate = demuxer->audioBitrate;
    int ret = aEncSample->CreateAudioEncoder(demuxer->audioMime);
    if (ret != AV_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "CreateAudioEncoder error");
        return ret;
    }
    ret = aEncSample->SetAudioEncoderCallback();
    if (ret != AV_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "AudioEncoder SetAudioEncoderCallback %{public}d", ret);
        return ret;
    }
    ret = aEncSample->ConfigureAudioEncoder();
    if (ret != AV_ERR_OK) {
        OH_LOG_ERROR(LOG_APP, "AudioEncoder ConfigureAudioEncoder %{public}d", ret);
        return ret;
    }
    aEncSample->RegisterMuxer(muxer.get());
    return ret;
}

int32_t CreateDemuxer(std::unique_ptr<DeMuxer> &demuxer, std::unique_ptr<MutexManager> &mutexManager,
                      AsyncCallbackInfo *asyncCallbackInfo)
{
    int32_t ret = demuxer->CreateDemuxer(asyncCallbackInfo->inputFd,
                                         asyncCallbackInfo->inputOffset, asyncCallbackInfo->inputSize);
    if (ret != AV_ERR_OK) {
            OH_LOG_ERROR(LOG_APP, "CreateDemuxer error %{public}d", ret);
            return ret;
        }
    demuxer->RegisterMuxerManager(mutexManager.get());
    return ret;
}

void CreateVideoDecode(std::unique_ptr<VDecNdkSample> &vDecSample, std::unique_ptr<MutexManager> &mutexManager,
    std::unique_ptr<DeMuxer> &demuxer, std::unique_ptr<VEncNdkSample> &vEncSample)
{
    vDecSample->RegisterMuxerManager(mutexManager.get());
    vDecSample->width = demuxer->width;
    vDecSample->height = demuxer->height;
    vDecSample->videoMime = demuxer->videoMime;
    if (demuxer->frameRate > 0) {
        vDecSample->defaultFrameRate = demuxer->frameRate;
    }
    vDecSample->vencNdkSample = vEncSample.get();
}

void CreateAudioDecode(std::unique_ptr<ADecNdkSample> &aDecSample, std::unique_ptr<MutexManager> &mutexManager,
    std::unique_ptr<DeMuxer> &demuxer)
{
    aDecSample->RegisterMuxerManager(mutexManager.get());
    aDecSample->audioBitrate = demuxer->audioBitrate;
    aDecSample->aacIsADTS = demuxer->aacIsADTS;
    aDecSample->audioSampleFormat = demuxer->audioSampleFormat;
    aDecSample->audioSampleRate = demuxer->audioSampleRate;
    aDecSample->audioChannelCount = demuxer->audioChannelCount;
}

void DealCallBack(napi_env env, void *data)
{
    AsyncCallbackInfo *asyncCallbackInfo = (AsyncCallbackInfo *)data;
    if (asyncCallbackInfo->resultCode != 0) {
        if (remove(asyncCallbackInfo->outputPath.data()) == 0) {
            OH_LOG_ERROR(LOG_APP, "delete outputFile");
        }
    }
    napi_value code;
    napi_create_int32(env, asyncCallbackInfo->resultCode, &code);
    napi_value value;
    napi_create_string_utf8(env, asyncCallbackInfo->resultStr.data(), NAPI_AUTO_LENGTH, &value);
    napi_value outputPath;
    OH_LOG_ERROR(LOG_APP, "callback outputPath:%{public}s", asyncCallbackInfo->outputPath.c_str());
    napi_create_string_utf8(env, asyncCallbackInfo->outputPath.data(), NAPI_AUTO_LENGTH, &outputPath);
    napi_value obj;
    napi_create_object(env, &obj);
    napi_set_named_property(env, obj, "code", code);
    napi_set_named_property(env, obj, "message", value);
    napi_set_named_property(env, obj, "outputPath", outputPath);
    napi_resolve_deferred(asyncCallbackInfo->env, asyncCallbackInfo->deferred, obj);
    napi_delete_async_work(env, asyncCallbackInfo->asyncWork);
    delete asyncCallbackInfo;
    asyncCallbackInfo = nullptr;
}

void VideoCompressorDestroy(std::unique_ptr<AEncNdkSample> &aEncSample, std::unique_ptr<ADecNdkSample> &aDecSample,
    std::unique_ptr<VDecNdkSample> &vDecSample, std::unique_ptr<VEncNdkSample> &vEncSample,
    std::unique_ptr<DeMuxer> &demuxer, std::unique_ptr<Muxer> &muxer)
{
    OH_LOG_ERROR(LOG_APP, "VideoCompressorDestroy");
    if (vDecSample != nullptr) {
        vDecSample->WaitForEos();
    }
    if (aDecSample != nullptr) {
        aDecSample->WaitForEos();
    }
    if (vEncSample != nullptr) {
        vEncSample->WaitForEos();
    }
    if (aEncSample != nullptr) {
        aEncSample->WaitForEos();
    }
    if (muxer != nullptr) {
        muxer->StopMuxer();
    }
    if (demuxer != nullptr) {
        demuxer->DestroyDemuxer();
    }
    vDecSample = nullptr;
    aDecSample = nullptr;
    vEncSample = nullptr;
    aEncSample = nullptr;
    muxer = nullptr;
    demuxer = nullptr;
    OH_LOG_ERROR(LOG_APP, "VideoCompressorDestroy end");
}

void SetCallBackResult(AsyncCallbackInfo *asyncCallbackInfo, int32_t code, std::string str)
{
    OH_LOG_ERROR(LOG_APP, "%{public}s", str.c_str());
    asyncCallbackInfo->resultCode = code;
    asyncCallbackInfo->resultStr = str;
}

void NativeCompressVideo(napi_env env, void *data)
{
    AsyncCallbackInfo *asyncCallbackInfo = (AsyncCallbackInfo *)data;
    auto mutexManager = std::make_unique<MutexManager>();
    // 创建demuxer
    auto demuxer = std::make_unique<DeMuxer>();
    // 创建muxer
    auto muxer = std::make_unique<Muxer>();
    // 创建视频编码器
    auto vEncSample = std::make_unique<VEncNdkSample>();
    // 创建音频编码器
    auto aEncSample = std::make_unique<AEncNdkSample>();
    // 创建视频解码器
    auto vDecSample = std::make_unique<VDecNdkSample>();
    // 创建音频编码器
    auto aDecSample = std::make_unique<ADecNdkSample>();
    if (CreateDemuxer(demuxer, mutexManager, asyncCallbackInfo) != AV_ERR_OK) {
        SetCallBackResult(asyncCallbackInfo, -1, "CreateDemuxer error");
        goto finish;
    }
    
    if (CreateMutex(muxer, demuxer, asyncCallbackInfo) != AV_ERR_OK) {
        SetCallBackResult(asyncCallbackInfo, -1, "CreateMuxer error");
        goto finish;
    }
    
    if (CreateVideoEncode(vEncSample, mutexManager, demuxer, muxer, asyncCallbackInfo) != AV_ERR_OK) {
        SetCallBackResult(asyncCallbackInfo, -1, "CreateVideoEncode error");
        goto finish;
    }
    
    if (CreateAudioEncode(aEncSample, mutexManager, demuxer, muxer) != AV_ERR_OK) {
        SetCallBackResult(asyncCallbackInfo, -1, "CreateAudioEncode error");
        goto finish;
    }
    
    CreateVideoDecode(vDecSample, mutexManager, demuxer, vEncSample);
    // 视频解码
    if (vDecSample->RunVideoDec(demuxer->videoMime) != AV_ERR_OK) {
        SetCallBackResult(asyncCallbackInfo, -1, "VideoDecoder decode error");
        goto finish;
    }

    CreateAudioDecode(aDecSample, mutexManager, demuxer);
    // 音频解码
    if (aDecSample->RunAudioDec(demuxer->audioMime) != AV_ERR_OK) {
        SetCallBackResult(asyncCallbackInfo, -1, "AudioDecoder decode error");
        goto finish;
    }
    
    // 解封装
    demuxer->StartDemux();
    
    // 视频编码
    if (vEncSample->StartVideoEncoder() != AV_ERR_OK) {
        SetCallBackResult(asyncCallbackInfo, -1, "videoEncode error");
        goto finish;
    }
    
    // 音频编码
    if (aEncSample->StartAudioEncoder() != AV_ERR_OK) {
        SetCallBackResult(asyncCallbackInfo, -1, "audioEncode error");
        goto finish;
    }
    SetCallBackResult(asyncCallbackInfo, 0, "videoCompressor success");
    finish:
    VideoCompressorDestroy(aEncSample, aDecSample, vDecSample, vEncSample, demuxer, muxer);
}
napi_value VideoCompressor::CompressVideo(napi_env env, napi_callback_info info)
{
    int32_t outFd = true;
    int32_t inputFd = true;
    int32_t inputOffset = 0;
    int32_t inputSize = 0;
    int32_t quality = 0;
    size_t argc = 6;
    int32_t positionTwo = 2;
    int32_t positionThree = 3;
    int32_t positionFour = 4;
    int32_t positionFive = 5;
    napi_value args[6] = {nullptr};
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
    napi_get_value_int32(env, args[0], &outFd);
    napi_get_value_int32(env, args[1], &inputFd);
    napi_get_value_int32(env, args[positionTwo], &inputOffset);
    napi_get_value_int32(env, args[positionThree], &inputSize);
    napi_get_value_int32(env, args[positionFour], &quality);
    size_t charLen = 0;
    int len = 1024;
    char output[1024] = {0};
    napi_get_value_string_utf8(env, args[positionFive], output, len, &charLen);
    OH_LOG_ERROR(LOG_APP, "outputPath:%{public}s", output);
    napi_deferred deferred;
    napi_value promise;
    napi_create_promise(env, &deferred, &promise);
    AsyncCallbackInfo *asyncCallbackInfo = new AsyncCallbackInfo{.env = env,
                                                                 .asyncWork = nullptr,
                                                                 .deferred = deferred,
                                                                 .outFd = outFd,
                                                                 .inputFd = inputFd,
                                                                 .inputOffset = inputOffset,
                                                                 .inputSize = inputSize,
                                                                 .quality = quality,
                                                                 .outputPath = output};
    napi_value resourceName;
    napi_create_string_latin1(env, "videoTest", NAPI_AUTO_LENGTH, &resourceName);
    napi_create_async_work(
        env, nullptr, resourceName, [](napi_env env, void *data) { NativeCompressVideo(env, data);},
        [](napi_env env, napi_status status, void *data) { DealCallBack(env, data); }, (void*)asyncCallbackInfo,
        &asyncCallbackInfo->asyncWork);
    napi_queue_async_work(env, asyncCallbackInfo->asyncWork);
    return promise;
}

EXTERN_C_START static napi_value Init(napi_env env, napi_value exports)
{
    napi_property_descriptor classProp[] = {
        {"compressVideoNative", nullptr, VideoCompressor::CompressVideo,
         nullptr, nullptr, nullptr, napi_default, nullptr},
    };
    napi_value videoCompressor = nullptr;
    const char *classBindName = "videoCompressor";
    napi_define_class(env, classBindName, sizeof(classBindName), VideoCompressor::JsConstructor, nullptr,
                      sizeof(classProp) / sizeof(classProp[0]), classProp, &videoCompressor);
    napi_set_named_property(env, exports, "newVideoCompressor", videoCompressor);
    return exports;
}
EXTERN_C_END

static napi_module VideoCompressorModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "videoCompressor",
    .nm_priv = ((void *)0),
    .reserved = {0},
};

extern "C" __attribute__((constructor)) void RegisterEntryModule(void) {napi_module_register(&VideoCompressorModule); }